package com.github.freegeese.maven.plugin.autocode;

import com.github.freegeese.maven.plugin.autocode.configuration.DefaultProperties;
import com.github.freegeese.maven.plugin.autocode.configuration.JdbcConnection;
import com.github.freegeese.maven.plugin.autocode.configuration.ModelType;
import com.github.freegeese.maven.plugin.autocode.configuration.Template;
import com.github.freegeese.maven.plugin.autocode.metadata.Column;
import com.github.freegeese.maven.plugin.autocode.metadata.Table;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import ognl.Ognl;
import ognl.OgnlException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Mojo(name = "autocode", threadSafe = true)
public class AutoCodeMojo extends AbstractMojo {

    @Parameter(property = "freegeese.autocode.properties")
    private Map<String, String> properties = new LinkedHashMap<>();

    @Parameter(property = "freegeese.autocode.jdbcConnection")
    private JdbcConnection jdbcConnection;

    @Parameter(property = "freegeese.autocode.tables")
    private List<Table> tables;

    @Parameter(property = "freegeese.autocode.templateDirectory")
    private File templateDirectory;

    @Parameter(property = "freegeese.autocode.templates")
    private List<Template> templates;

    private List<Table> dbTables = null;
    private Configuration templateConfiguration = null;

    public AutoCodeMojo() throws IOException {
        templateConfiguration = new Configuration(Configuration.VERSION_2_3_24);
        templateConfiguration.setDefaultEncoding("UTF-8");
    }

    public void execute() throws MojoExecutionException {
        Log log = getLog();
        try {
            templateConfiguration.setDirectoryForTemplateLoading(templateDirectory);
            // 属性设置
            String customCodeStart = DefaultProperties.CUSTOM_CODE_START.name();
            if (!properties.containsKey(customCodeStart)) {
                properties.put(customCodeStart, customCodeStart);
            }
            String customCodeEnd = DefaultProperties.CUSTOM_CODE_END.name();
            if (!properties.containsKey(customCodeEnd)) {
                properties.put(customCodeEnd, customCodeEnd);
            }

            // 获取数据库所有的Table信息
            log.info("获取数据连接：" + jdbcConnection);
            Connection conn = DatabaseUtils.getConnection(jdbcConnection);

            // 获取数据库所有表格信息
            log.info("获取数据库所有表格元数据");
            this.dbTables = DatabaseMetadataUtils.getAllTableMetadata(conn);
            DatabaseUtils.closeConnection(conn);

            // 校验配置的信息
            if (validateConfiguration(dbTables, tables)) {
                // 解析Table配置信息
                List<Table> filteredTables = new ArrayList<>();
                for (Table cfgTable : this.tables) {
                    // 匹配Table元数据
                    Table dbTable = findTableMetadata(cfgTable.getTableCat(), cfgTable.getTableSchem(), cfgTable.getTableName());
                    // 完善Table配置信息
                    initTableConfiguration(cfgTable, dbTable);
                    // 合并数据库Table信息
                    Table merged = new Table();
                    BeanPropertyUtils.merge(merged, dbTable, cfgTable);
                    // model type
                    ModelType modelType = merged.getModelType();
                    if (null != modelType) {
                        modelType.setTable(merged);
                    }
                    filteredTables.add(merged);
                    // TODO: 2017/4/1 校验Table配置
                }

                // 根据模板配置，生成代码
                for (Table table : filteredTables) {
                    // 一个Table对应所有模板
                    for (Template template : templates) {
                        generateCode(table, template);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean validateConfiguration(List<Table> dbTables, List<Table> tables) {
        List<String> dbTableNames = new ArrayList<>();
        for (Table dbTable : dbTables) {
            dbTableNames.add(dbTable.getTableName());
        }

        for (Table table : tables) {
            String tableName = table.getTableName();
            if (!dbTableNames.contains(tableName)) {
                getLog().info("不存在的数据库表:[" + tableName + "], 所有数据库表:[" + dbTableNames + "]");
                return false;
            }
        }
        return true;
    }

    private void generateCode(Table table, Template template) {
        FileWriter writer = null;
        try {
            Template initialized = initTemplateConfiguration(table, template);
            freemarker.template.Template ftl = templateConfiguration.getTemplate(initialized.getName());
            String outputPath = initialized.getOutputPath();
            File outputFile = new File(outputPath);
            // output file exists and template not override
            if (outputFile.exists() && !template.isOverride()) {
                getLog().info("output file exists(" + outputFile + ") -> skip");
                return;
            }
            if (!outputFile.getParentFile().exists()) {
                outputFile.getParentFile().mkdirs();
            }
            // process custom code
            Map<String, Object> parameterMap = initialized.getParameterMap();
            if (outputFile.exists()) {
                String customCodeStart = initialized.getCustomCodeStart();
                String customCodeEnd = initialized.getCustomCodeEnd();
                // 已存在的标记内容
                String oldMarkContent = Helper.findMarkContent(outputFile, customCodeStart, customCodeEnd);
                writer = new FileWriter(outputFile);
                ftl.process(parameterMap, writer);
                // 根据模板生成的标记内容
                String newMarkContent = Helper.findMarkContent(outputFile, customCodeStart, customCodeEnd);
                if (null != oldMarkContent && null != newMarkContent) {
                    Path path = Paths.get(outputFile.getAbsolutePath());
                    String content = new String(Files.readAllBytes(path));
                    content = content.replace(newMarkContent, oldMarkContent);
                    Files.write(path, content.getBytes());
                } else if (null != oldMarkContent) {
                    Helper.appendToTail(outputFile, oldMarkContent);
                }
            } else {
                writer = new FileWriter(outputFile);
                ftl.process(parameterMap, writer);
            }
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        } finally {
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Template initTemplateConfiguration(Table table, Template template) {
        Template merged = new Template();
        BeanPropertyUtils.merge(merged, template);

        // template parameter merge
        Map<String, Object> parameters = new LinkedHashMap<>();
        parameters.put("table", table);
        // global properties
        parameters.putAll(properties);
        // template parameter
        Map<String, Object> parameterMap = merged.getParameterMap();
        if (null != parameterMap) {
            parameters.putAll(parameterMap);
        }
        merged.setParameterMap(parameters);

        // template output path
        String outputPath = merged.getOutputPath();
        String regex = "(\\{[^{}]+})";
        Matcher m = Pattern.compile(regex).matcher(outputPath.toString());
        while (m.find()) {
            String group = m.group();
            String expression = group.substring(1, group.length() - 1).trim();
            try {
                Object expressionResult = Ognl.getValue(expression, merged.getParameterMap());
                outputPath = outputPath.replaceFirst("\\" + group, expressionResult.toString());
            } catch (OgnlException e) {
                e.printStackTrace();
            }
        }
        merged.setOutputPath(outputPath);

        // custom code mark
        if (null == merged.getCustomCodeStart()) {
            merged.setCustomCodeStart(properties.get(DefaultProperties.CUSTOM_CODE_START.name()));
        }
        if (null == merged.getCustomCodeEnd()) {
            merged.setCustomCodeEnd(properties.get(DefaultProperties.CUSTOM_CODE_END.name()));
        }

        return merged;
    }

    /**
     * 初始化Table配置信息
     *
     * @param cfgTable
     * @param dbTable
     * @return
     */
    private Table initTableConfiguration(Table cfgTable, Table dbTable) {
        Log log = getLog();
        // init column configuration
        log.info("init column configuration");
        // 过滤后的列（这些列是最终确定需要的列）
        List<Column> filteredColumns = new ArrayList<>();
        List<Column> columns = dbTable.getColumns();
        for (Column dbColumn : columns) {
            Column column = initColumnConfiguration(cfgTable, dbColumn);
            // ignore column
            if (null == column) {
                log.info("ignore column: " + dbColumn.getColumnName());
                continue;
            }
            filteredColumns.add(column);
        }
        cfgTable.setFilteredColumns(filteredColumns);

        // init model name
        log.info("init model name");
        if (null == cfgTable.getModelName()) {
            cfgTable.setModelName(DatabaseMetadataUtils.underlineToCamelHump(cfgTable.getTableName()));
        }

        return cfgTable;
    }


    /**
     * 初始化Column配置信息
     *
     * @param cfgTable
     * @param column
     * @return
     */
    private Column initColumnConfiguration(Table cfgTable, Column column) {
        List<String> includeColumns = cfgTable.getIncludeColumns();
        String columnName = column.getColumnName();
        // 配置了include columns 但是没有包含此列
        if (null != includeColumns && !includeColumns.contains(columnName)) {
            return null;
        }

        // 配置了exclude columns 包含此列
        List<String> excludeColumns = cfgTable.getExcludeColumns();
        if (null != excludeColumns && excludeColumns.contains(columnName)) {
            return null;
        }

        // 主键列处理
        Column idColumn = cfgTable.getIdColumn();
        // 已配置主键列
        if (null != idColumn && columnName.equalsIgnoreCase(idColumn.getColumnName())) {
            return idColumn;
        }
        // 未配置主键列
        if (null == idColumn && column.isPrimaryKey()) {
            cfgTable.setIdColumn(column);
            return column;
        }

        // 获取覆盖此列的配置
        Column overrideColumn = findOverrideColumn(cfgTable, column);
        if (null != overrideColumn) {
            Column merged = new Column();
            BeanPropertyUtils.merge(merged, column, overrideColumn);
            return merged;
        }
        return column;
    }

    /**
     * 匹配覆盖的列
     *
     * @param table
     * @param column
     * @return
     */
    private Column findOverrideColumn(Table table, Column column) {
        List<Column> overrideColumns = table.getColumns();
        if (null == overrideColumns) {
            return null;
        }
        for (Column overrideColumn : overrideColumns) {
            if (overrideColumn.getColumnName().equalsIgnoreCase(column.getColumnName())) {
                return overrideColumn;
            }
        }
        return null;
    }

    /**
     * 匹配对应的Table元数据
     *
     * @param catalog
     * @param schema
     * @param tableName
     * @return
     */
    private Table findTableMetadata(String catalog, String schema, String tableName) {
        for (Table t : this.dbTables) {
            if (null == catalog && null == schema) {
                if (tableName.equalsIgnoreCase(t.getTableName())) {
                    return t;
                }
                continue;
            }
            if (null == catalog) {
                if (catalog.equalsIgnoreCase(t.getTableCat()) && tableName.equalsIgnoreCase(t.getTableName())) {
                    return t;
                }
                continue;
            }
            if (null == schema) {
                if (schema.equalsIgnoreCase(t.getTableSchem()) && tableName.equalsIgnoreCase(t.getTableName())) {
                    return t;
                }
                continue;
            }
            if (catalog.equalsIgnoreCase(t.getTableCat()) && schema.equalsIgnoreCase(t.getTableSchem()) && tableName.equalsIgnoreCase(t.getTableName())) {
                return t;
            }
        }
        return null;
    }
}
